using System;
using System.Data;
using System.Text;
using System.Globalization;

using BizElements.Core.ProviderComponents;

namespace BizElements.Core
{    
    #region Documentation.
    /// <summary>
    /// Contains database column metadata and methods.
    /// </summary>
    /// <remarks>Contains metadata for the database columns it represents.
    /// </remarks>
    /// <example>
    /// The following example uses generated table and column metadata to select data from tables Territories and Region.
    /// <code>
    /// public DataTable SelectTerritoriesAndRegions()
    /// {			
    /// 	// SELECT Territories.TerritoryDescription, Region.RegionDescription
    /// 	// FROM Territories
    /// 	//		INNER JOIN Region ON Territories.RegionID = Region.RegionID
    /// 
    /// 	TerritoriesMeta territories = new TerritoriesMeta();
    /// 	RegionMeta regions = new RegionMeta();			
    /// 
    /// 	SelectStatement select = new SelectStatement(territories);
    /// 	select.SelectList.Add(territories.TerritoryDescription);
    /// 	select.SelectList.Add(regions.RegionDescription);
    /// 	select.Relations.Add(new DbRelation(regions, territories, territories.RegionID));
    /// 
    /// 	DataTable territoryAndRegionNames = select.Execute();
    /// 	return territoryAndRegionNames;
    /// }
    /// </code>
    /// </example>
    /// <seealso cref="DbColumnExtensions"/><seealso cref="IDbTable"/>
    #endregion
    [Serializable]
    //[Obsolete("Preffer using SealedDbColumn class instead of DbColumn. SealedDbColumn class supports usage/design patterns which significantly optimize memory usage.")]
    public class DbColumn : IDbColumn
    {
        #region Configuration & constants.

        /// <summary>
        /// Separator between table and column name in prefixed alias.
        /// Eg: Country_IdCountry.
        /// TODO: read from *.config file.
        /// </summary>
        const string PrefixedAliasSeparator = "_";

        #endregion

        #region Members.

        /// <summary>
        /// Indicates whether the setters may be used to change this object.
        /// </summary>
        bool isReadOnly;

        /// <summary>
        /// DB data type.
        /// </summary>
        DbType dbType;

        /// <summary>
        /// Column alias. By default it is equal to column name.
        /// </summary>
        string alias;

        /// <summary>
        /// Table/view to which this column belongs to.
        /// </summary>
        IDbTable table;

        /// <summary>
        /// Indicates whether the column is a part of the primary key.
        /// </summary>
        bool isPrimaryKeyPart;

        /// <summary>
        /// Indicates whether the column is a part of a foreign key.
        /// </summary>
        bool isForeignKeyPart;

        /// <summary>
        /// Indicates whether the column is a part of a unique constraint.
        /// </summary>
        bool isUniqueConstraintPart;

        /// <summary>
        /// The smallest possible value of this column. May be <b>null</b>.
        /// </summary>
        object minValue;

        /// <summary>
        /// The smallest possible value of this column. May be <b>null</b>.
        /// </summary>
        object maxValue;

        /// <summary>
        /// Indicates whether null values are allowed in this column.
        /// </summary>
        bool isNullable;

        /// <summary>
        /// Name of the class property mapped to this column.
        /// Usefull when binding entity collections.
        /// </summary>
        string propertyName;

        /// <summary>
        /// Indicates whether the value of the field is automatically generated by database (eg. timestamp in SQL Server).
        /// If set to true then this field's value cannot be inserted or updated.
        /// </summary>
        bool isAutoGenerated;

        /// <summary>
        /// Sequence used to increment values for this field. Ignored if field is not auto-incremented.
        /// Must be <b>null</b> if AutoIncrement flag is set to false.
        /// Has no effect for DBMS types that don't support sequences.
        /// </summary>
        string sequenceName;


        // The members below have similar names and purposes as System.Data.DataColumn properties.

        /// <summary>
        /// Indicates whether the column automatically increments the value for new rows added to the table.
        /// Column must be an integer.
        /// </summary>
        bool autoIncrement;

        /// <summary>
        /// Name of the column in database.
        /// </summary>
        string columnName;

        /// <summary>
        /// .NET type of data stored in the column.
        /// </summary>
        Type dataType = typeof(object);

        /// <summary>
        /// Default value for the column when creating new rows.
        /// </summary>
        object defaultValue;

        /// <summary>
        /// Maximum length of a text column.
        /// </summary>
        int maxLengthIfText = -1;

        /// <summary>
        /// The position of the column in the table/view.
        /// </summary>
        int ordinal = -1;

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of the DbColumn class.
        /// </summary>
        /// <remarks>Default constructor. Inherited class may set members via Init method.</remarks>
        protected DbColumn()
        { 
        }

        /// <summary>
        /// Initializes a new instance of the DbColumn class.
        /// </summary>
        /// <param name="columnName">Column name.</param>
        /// <param name="table">Table or view to which this column belongs to.</param>
        /// <param name="alias">Column alias. If <b>null</b> then it is equal to column name.</param>
        /// <param name="dbType">DB data type.</param>
        /// <param name="dataType">.NET type of data stored in the column.</param>
        /// <param name="nullable">Specifies whether the values of this column are nullable.</param>
        /// <param name="ordinal">Column's ordinal in parent table.</param>
        /// <param name="autoIncrement">Specifies whether the values of this column are automatically incremented.</param>
        /// <param name="defaultValue">Default value.</param>
        /// <param name="maxLengthIfText">The maximum length of a text column.</param>
        /// <param name="isPrimaryKeyPart">Indicates whether the column is a part of the primary key.</param>
        /// <param name="isForeignKeyPart">Indicates whether the column is a part of a foreign key.</param>
        /// <param name="isUniqueConstraintPart">Indicates whether the column is a part of a unique constraint.</param>
        /// <param name="propertyName">Name of the class property mapped to this column.</param>		
        /// <param name="minValue">The smallest possible value of this column. May be <b>null</b>.</param>
        /// <param name="maxValue">The smallest possible value of this column. May be <b>null</b>.</param>
        /// <param name="isAutoGenerated">Indicates whether the value of the field is automatically generated by database (eg. timestamp in SQL Server).</param>
        /// <param name="sequenceName">Sequence that increments value for this field. <b>Null</b> if the field is not auto-incremented or sequence does not exist.</param>
        public DbColumn(string columnName, IDbTable table, string alias, DbType dbType, Type dataType, bool nullable, int ordinal, bool autoIncrement, object defaultValue, int maxLengthIfText, bool isPrimaryKeyPart, bool isForeignKeyPart, bool isUniqueConstraintPart, string propertyName, object minValue, object maxValue, bool isAutoGenerated, string sequenceName)
        {
            Init(columnName, table, alias, dbType, dataType, nullable, ordinal, autoIncrement, defaultValue, maxLengthIfText, isPrimaryKeyPart, isForeignKeyPart, isUniqueConstraintPart, propertyName, minValue, maxValue, isAutoGenerated, sequenceName);
        }

        /// <summary>
        /// Initializes object. Inherited class may set members via Init method.
        /// </summary>
        /// <param name="columnName">Column name.</param>
        /// <param name="table">Table or view to which this column belongs to.</param>
        /// <param name="alias">Column alias. If <b>null</b> then it is equal to column name.</param>
        /// <param name="dbType">DB data type.</param>
        /// <param name="dataType">.NET type of data stored in the column.</param>
        /// <param name="nullable">Specifies whether the values of this column are nullable.</param>
        /// <param name="ordinal">Column's ordinal in parent table.</param>
        /// <param name="autoIncrement">Specifies whether the values of this column are automatically incremented.</param>
        /// <param name="defaultValue">Default value.</param>
        /// <param name="maxLengthIfText">The maximum length of a text column.</param>
        /// <param name="isPrimaryKeyPart">Indicates whether the column is a part of the primary key.</param>
        /// <param name="isForeignKeyPart">Indicates whether the column is a part of a foreign key.</param>
        /// <param name="isUniqueConstraintPart">Indicates whether the column is a part of a unique constraint.</param>
        /// <param name="propertyName">Name of the class property mapped to this column.</param>
        /// <param name="minValue">The smallest possible value of this column. May be <b>null</b>.</param>
        /// <param name="maxValue">The smallest possible value of this column. May be <b>null</b>.</param>
        /// <param name="isAutoGenerated">Indicates whether the value of the field is automatically generated by database (eg. timestamp in SQL Server).</param>
        /// <param name="sequenceName">Sequence that increments value for this field. <b>Null</b> if the field is not auto-incremented or sequence does not exist.</param>
        protected void Init(string columnName, IDbTable table, string alias, DbType dbType, Type dataType, bool nullable, int ordinal, bool autoIncrement, object defaultValue, int maxLengthIfText, bool isPrimaryKeyPart, bool isForeignKeyPart, bool isUniqueConstraintPart, string propertyName, object minValue, object maxValue, bool isAutoGenerated, string sequenceName)
        {
            this.columnName = columnName;
            this.table = table;
            this.isNullable = nullable;
            this.dataType = dataType;
            this.ordinal = ordinal;
            this.minValue = minValue;
            this.maxValue = maxValue;

            this.autoIncrement = autoIncrement;
            this.sequenceName = (autoIncrement) ? sequenceName : null;

            if (defaultValue != null && autoIncrement == false)
                this.defaultValue = defaultValue;
            if (maxLengthIfText >= 0)
                this.maxLengthIfText = maxLengthIfText;

            this.alias = (alias != null) ? alias : columnName;
            this.dbType = dbType;

            this.isPrimaryKeyPart = isPrimaryKeyPart;
            this.isForeignKeyPart = isForeignKeyPart;
            this.isUniqueConstraintPart = isUniqueConstraintPart;
            this.propertyName = propertyName;
            this.isAutoGenerated = isAutoGenerated;
        }

        #endregion

        #region Methods.

        /// <summary>
        /// Computes a string which is a combination of prefix (usually table name) and column alias.
        /// Eg: Id -> MyTable_Id
        /// </summary>
        /// <returns>Prefixed alias.</returns>
        public virtual string GetPrefixedAlias()
        {
            return this.Prefix + this.ColumnName;
        }

        #endregion

        private static string CleanTableAlias(string alias)
        {
            return alias.Replace("[", "").Replace("]", "").Replace(" ", "_");
        }

        private void ThrowExceptionIfIsReadOnly()
        {
            if (this.isReadOnly)
                throw new InvalidOperationException(Messages.DbColumn_DbColumnIsReadOnlyDataMayNotBeAltered);
        }

        /// <summary>
        /// Gets a value which indicates whether the metadata for this column may be changed.
        /// </summary>
        public bool IsReadOnly
        {
            get { return this.isReadOnly; }
            set { this.isReadOnly = value; }
        }

        #region Properties that honnor isReadOnly flag.

        /// <summary>
        /// Gets or sets the <see cref="DbType"/> of the column.
        /// Changes of this property affect the other type properties.
        /// </summary>
        public DbType DbType
        {
            get { return this.dbType; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.dbType = value;
                this.dataType = TypeUtil.NetTypeFrom(value);
            }
        }

        /// <summary>
        /// Gets alias.
        /// </summary>
        public string Alias
        {
            get 
            {
                bool shouldReturnPrefixedAlias = (this.table != null) && this.table.ColumnAliasesArePrefixed;
                return (shouldReturnPrefixedAlias) ? GetPrefixedAlias() : this.alias; 
            }
        }

        /// <summary>
        /// Gets alias prefix. Eg: "MyTable_". Empty string if parent table is not set.
        /// </summary>
        public string Prefix
        {
            get
            {
                if (this.table != null)
                    return CleanTableAlias(this.table.Alias) + PrefixedAliasSeparator;
                else
                    return "";
            }
        }

        /// <summary>
        /// Gets or sets the value that indicates whether null values are allowed in this column.
        /// </summary>
        public bool IsNullable
        {
            get { return this.isNullable; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.isNullable = value;
            }
        }

        /// <summary>
        /// Gets or sets the value that indicates whether the column automatically increments the value for new rows added to the table.
        /// </summary>
        public bool AutoIncrement
        {
            get { return this.autoIncrement; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.autoIncrement = value;
            }
        }

        /// <summary>
        /// Gets or sets the name of the column.
        /// </summary>
        public string ColumnName
        {
            get { return this.columnName; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.columnName = value;
            }
        }

        /// <summary>
        /// Gets or sets .NET type of data stored in the column.
        /// Changes of this property affect the database type property.
        /// </summary>
        public Type DataType
        {
            get { return this.dataType; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.dataType = value;
                this.dbType = TypeUtil.DbTypeFrom(value);
            }
        }

        /// <summary>
        /// Gets or sets the default value for the column when creating new rows.
        /// </summary>
        public object DefaultValue
        {
            get { return this.defaultValue; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.defaultValue = value;
            }
        }

        /// <summary>
        /// Gets or sets the maximum length of a text column.
        /// Has no effect it it is not a text column.
        /// </summary>
        public int MaxLengthIfText
        {
            get { return this.maxLengthIfText; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.maxLengthIfText = value;
            }
        }

        /// <summary>
        /// Gets the position of the column in the database table/view.
        /// </summary>
        public int Ordinal
        {
            get { return this.ordinal; }
            //set {ThrowExceptionIfIsReadOnly(); this.ordinal = value;}
        }


        /// <summary>
        /// Gets or sets <see cref="IDbTable"/> object to which this column belongs to.
        /// </summary>
        public IDbTable Table
        {
            get { return this.table; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.table = value;
            }
        }

        /// <summary>
        /// Gets or sets the value that indicates whether the column is a part of the primary key.
        /// </summary>
        public bool IsPrimaryKeyPart
        {
            get { return this.isPrimaryKeyPart; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.isPrimaryKeyPart = value;
            }
        }

        /// <summary>
        /// Gets or sets the value that indicates whether the column is a part of a foreign key.
        /// </summary>
        public bool IsForeignKeyPart
        {
            get { return this.isForeignKeyPart; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.isForeignKeyPart = value;
            }
        }

        /// <summary>
        /// Gets or sets the value that indicates whether the column is a part of a unique constraint.
        /// </summary>
        public bool IsUniqueConstraintPart
        {
            get { return this.isUniqueConstraintPart; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.isUniqueConstraintPart = value;
            }
        }

        /// <summary>
        /// Gets or sets the smallest possible value of this column. May be <b>null</b>.
        /// </summary>
        public object MinValue
        {
            get { return this.minValue; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.minValue = value;
            }
        }

        /// <summary>
        /// Gets or sets the smallest possible value of this column. May be <b>null</b>.
        /// </summary>
        public object MaxValue
        {
            get { return this.maxValue; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.maxValue = value;
            }
        }

        /// <summary>
        /// Gets or sets the name of the generated entity class property mapped to this column.
        /// </summary>
        public string PropertyName
        {
            get { return this.propertyName; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.propertyName = value;
            }
        }

        /// <summary>
        /// Gets or sets the value which indicates whether the value of the field is automatically generated by
        /// database (eg. timestamp or computed fields in SQL Server). 
        /// If set to true then this field's value cannot be inserted or updated.
        /// </summary>
        public bool IsAutoGenerated
        {
            get { return this.isAutoGenerated; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                this.isAutoGenerated = value;
            }
        }

        /// <summary>
        /// Gets or sets the sequence used to increment values for this field.
        /// Must be <b>null</b> if AutoIncrement flag is set to false.
        /// </summary>
        public string SequenceName
        {
            get { return this.sequenceName; }
            set
            {
                ThrowExceptionIfIsReadOnly();
                if (value != null && !this.autoIncrement)
                    throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, Messages.DbColumn_CantSetSeqForXBecauseNotAutoIncrement, this.columnName));

                this.sequenceName = value;
            }
        }

        /// <summary>Not implemented. Always returns <b>null</b>.</summary>
        /// <value><b>null</b> meaning the implementation does not support custom information.</value>
        public IPropertyCollection<string> ExtendedProperties
        {
            get { return null; }
        }

        #endregion
    }    
}